const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
+const GMenu = imports.gi.GMenu;
const Shell = imports.gi.Shell;
const Lang = imports.lang;
const Signals = imports.signals;
this._appSystem = Shell.AppSystem.get_default();
this._pendingAppLaterId = 0;
- this._apps = [];
- this._filterApp = null;
+ this._appIcons = {}; // desktop file id
let box = new St.BoxLayout({ vertical: true });
box.add(this._grid.actor, { y_align: St.Align.START, expand: true });
_removeAll: function() {
this._grid.removeAll();
- this._apps = [];
+ this._appIcons = {};
},
- _addApp: function(appInfo) {
- let appIcon = new AppWellIcon(this._appSystem.get_app(appInfo.get_id()));
+ _addApp: function(app) {
+ var id = app.get_id();
+ let appIcon = new AppWellIcon(app);
this._grid.addItem(appIcon.actor);
appIcon.actor.connect('key-focus-in', Lang.bind(this, this._ensureIconVisible));
- appIcon._appInfo = appInfo;
- if (this._filterApp && !this._filterApp(appInfo))
- appIcon.actor.hide();
-
- this._apps.push(appIcon);
+ this._appIcons[id] = appIcon;
},
_ensureIconVisible: function(icon) {
transition: 'easeOutQuad' });
},
- setFilter: function(filter) {
- this._filterApp = filter;
- for (let i = 0; i < this._apps.length; i++)
- this._apps[i].actor.visible = filter(this._apps[i]._appInfo);
- },
-
- // Create actors for the applications in an idle to avoid blocking
- // for too long; see bug 647778
- _addPendingApps: function() {
- let i;
- let startTimeMillis = new Date().getTime();
- for (i = 0; i < this._pendingAppIds.length; i++) {
- let id = this._pendingAppIds[i];
- this._addApp(this._pendingApps[id]);
-
- let currentTimeMillis = new Date().getTime();
- if (currentTimeMillis - startTimeMillis > MAX_APPLICATION_WORK_MILLIS)
- break;
- }
- this._pendingAppIds.splice(0, i + 1);
- if (this._pendingAppIds.length > 0) {
- return true;
+ setVisibleApps: function(apps) {
+ if (apps == null) { // null implies "all"
+ for (var id in this._appIcons) {
+ var icon = this._appIcons[id];
+ icon.actor.visible = true;
+ }
} else {
- this._pendingAppLaterId = 0;
- this._pendingAppIds = null;
- this._pendingApps = null;
- return false;
+ // Set everything to not-visible, then set to visible what we should see
+ for (var id in this._appIcons) {
+ var icon = this._appIcons[id];
+ icon.actor.visible = false;
+ }
+ for (var i = 0; i < apps.length; i++) {
+ var app = apps[i];
+ var id = app.get_id();
+ var icon = this._appIcons[id];
+ icon.actor.visible = true;
+ }
}
},
- refresh: function(apps) {
- let ids = [];
- for (let i in apps)
- ids.push(i);
- ids.sort(function(a, b) {
- return apps[a].get_name().localeCompare(apps[b].get_name());
- });
-
+ setAppList: function(apps) {
this._removeAll();
-
- this._pendingAppIds = ids;
- this._pendingApps = apps;
- if (this._pendingAppLaterId)
- Meta.later_remove(this._pendingAppLaterId);
- this._pendingAppLaterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
- Lang.bind(this, this._addPendingApps));
+ for (var i = 0; i < apps.length; i++) {
+ var app = apps[i];
+ this._addApp(app);
+ }
}
};
// -2 is a flag to indicate that nothing is selected
// (used only before the actor is mapped the first time)
this._currentCategory = -2;
- this._filters = new St.BoxLayout({ vertical: true, reactive: true });
- this._filtersBox = new St.ScrollView({ x_fill: false,
- y_fill: false,
- style_class: 'vfade' });
- this._filtersBox.add_actor(this._filters);
+ this._categories = [];
+ this._apps = null;
+
+ this._categoryBox = new St.BoxLayout({ vertical: true, reactive: true });
+ this._categoryScroll = new St.ScrollView({ x_fill: false,
+ y_fill: false,
+ style_class: 'vfade' });
+ this._categoryScroll.add_actor(this._categoryBox);
this.actor.add(this._view.actor, { expand: true, x_fill: true, y_fill: true });
- this.actor.add(this._filtersBox, { expand: false, y_fill: false, y_align: St.Align.START });
+ this.actor.add(this._categoryScroll, { expand: false, y_fill: false, y_align: St.Align.START });
// Always select the "All" filter when switching to the app view
this.actor.connect('notify::mapped', Lang.bind(this,
function() {
- if (this.actor.mapped && this._allFilter)
+ if (this.actor.mapped && this._allCategoryButton)
this._selectCategory(-1);
}));
- this._sections = [];
-
// We need a dummy actor to catch the keyboard focus if the
// user Ctrl-Alt-Tabs here before the deferred work creates
// our real contents
this._currentCategory = num;
- if (num != -1)
- this._allFilter.remove_style_pseudo_class('selected');
- else
- this._allFilter.add_style_pseudo_class('selected');
-
- this._view.setFilter(Lang.bind(this, function(app) {
- if (num == -1)
- return true;
- return this._sections[num].name == app.get_section();
- }));
+ if (num != -1) {
+ var category = this._categories[num];
+ this._allCategoryButton.remove_style_pseudo_class('selected');
+ this._view.setVisibleApps(category.apps);
+ } else {
+ this._allCategoryButton.add_style_pseudo_class('selected');
+ this._view.setVisibleApps(null);
+ }
- for (let i = 0; i < this._sections.length; i++) {
+ for (var i = 0; i < this._categories.length; i++) {
if (i == num)
- this._sections[i].filterActor.add_style_pseudo_class('selected');
+ this._categories[i].button.add_style_pseudo_class('selected');
else
- this._sections[i].filterActor.remove_style_pseudo_class('selected');
+ this._categories[i].button.remove_style_pseudo_class('selected');
+ }
+ },
+
+ // Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
+ _loadCategory: function(dir, appList) {
+ var iter = dir.iter();
+ var nextType;
+ while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
+ if (nextType == GMenu.TreeItemType.ENTRY) {
+ var entry = iter.get_entry();
+ var app = this._appSystem.lookup_app_by_tree_entry(entry);
+ appList.push(app);
+ } else if (nextType == GMenu.TreeItemType.DIRECTORY) {
+ _loadCategory(iter.get_directory(), appList);
+ }
}
},
- _addFilter: function(name, num) {
+ _addCategory: function(name, index, dir, allApps) {
let button = new St.Button({ label: GLib.markup_escape_text (name, -1),
style_class: 'app-filter',
x_align: St.Align.START,
can_focus: true });
- this._filters.add(button, { expand: true, x_fill: true, y_fill: false });
button.connect('clicked', Lang.bind(this, function() {
- this._selectCategory(num);
+ this._selectCategory(index);
}));
- if (num != -1)
- this._sections[num] = { filterActor: button,
- name: name };
- else
- this._allFilter = button;
+ var apps;
+ if (dir == null) {
+ apps = allApps;
+ this._allCategoryButton = button;
+ } else {
+ apps = [];
+ this._loadCategory(dir, apps);
+ this._categories.push({ apps: apps,
+ name: name,
+ button: button });
+ }
+
+ this._categoryBox.add(button, { expand: true, x_fill: true, y_fill: false });
},
_removeAll: function() {
- this._sections = [];
- this._filters.destroy_children();
+ this._categories = [];
+ this._categoryBox.destroy_children();
},
- refresh: function(apps) {
+ refresh: function() {
this._removeAll();
- let sections = this._appSystem.get_sections();
- this._apps = apps;
+ var allApps = Shell.AppSystem.get_default().get_all();
+ allApps.sort(function(a, b) {
+ return a.compare_by_name(b);
+ });
/* Translators: Filter to display all applications */
- this._addFilter(_("All"), -1);
-
- if (!sections)
- return;
-
- for (let i = 0; i < sections.length; i++)
- this._addFilter(sections[i], i);
+ this._addCategory(_("All"), -1, null, allApps);
+
+ var tree = this._appSystem.get_tree();
+ var root = tree.get_root_directory();
+
+ var iter = root.iter();
+ var nextType;
+ var i = 0;
+ while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
+ if (nextType == GMenu.TreeItemType.DIRECTORY) {
+ var dir = iter.get_directory();
+ this._addCategory(dir.get_name(), i, dir);
+ i++;
+ }
+ }
+ this._view.setAppList(allApps);
this._selectCategory(-1);
- this._view.refresh(apps);
if (this._focusDummy) {
let focused = this._focusDummy.has_key_focus();
},
_redisplay: function() {
- let apps = this._appSystem.get_flattened_apps().filter(function(app) {
- return !app.get_is_nodisplay();
- });
-
- this._appView.refresh(apps);
+ this._appView.refresh();
}
};
-function BaseAppSearchProvider() {
+function AppSearchProvider() {
this._init();
}
-BaseAppSearchProvider.prototype = {
+AppSearchProvider.prototype = {
__proto__: Search.SearchProvider.prototype,
- _init: function(name) {
- Search.SearchProvider.prototype._init.call(this, name);
+ _init: function() {
+ Search.SearchProvider.prototype._init.call(this, _("APPLICATIONS"));
this._appSys = Shell.AppSystem.get_default();
},
- getResultMeta: function(resultId) {
- let app = this._appSys.get_app(resultId);
- if (!app)
- return null;
- return { 'id': resultId,
+ getResultMeta: function(app) {
+ return { 'id': app,
'name': app.get_name(),
'createIcon': function(size) {
return app.create_icon_texture(size);
};
},
+ getInitialResultSet: function(terms) {
+ return this._appSys.initial_search(terms);
+ },
+
+ getSubsearchResultSet: function(previousResults, terms) {
+ return this._appSys.subsearch(previousResults, terms);
+ },
+
activateResult: function(id, params) {
params = Params.parse(params, { workspace: null,
timestamp: null });
let modifiers = event ? Shell.get_event_state(event) : 0;
let openNewWindow = modifiers & Clutter.ModifierType.CONTROL_MASK;
- let app = this._appSys.get_app(id);
+ let app = this._appSys.lookup_app(id);
if (openNewWindow)
app.open_new_window(workspace);
else
params = Params.parse(params, { workspace: null,
timestamp: null });
- let app = this._appSys.get_app(id);
+ let app = this._appSys.lookup_app(id);
app.open_new_window(params.workspace ? params.workspace.index() : -1);
+ },
+
+ createResultActor: function (resultMeta, terms) {
+ let app = resultMeta['id'];
+ let icon = new AppWellIcon(app);
+ return icon.actor;
}
};
-function AppSearchProvider() {
+function SettingsSearchProvider() {
this._init();
}
-AppSearchProvider.prototype = {
- __proto__: BaseAppSearchProvider.prototype,
+SettingsSearchProvider.prototype = {
+ __proto__: Search.SearchProvider.prototype,
_init: function() {
- BaseAppSearchProvider.prototype._init.call(this, _("APPLICATIONS"));
+ Search.SearchProvider.prototype._init.call(this, _("SETTINGS"));
+ this._appSys = Shell.AppSystem.get_default();
+ this._gnomecc = this._appSys.lookup_app('gnome-control-center.desktop');
+ },
+
+ getResultMeta: function(pref) {
+ return { 'id': pref,
+ 'name': pref.get_name(),
+ 'createIcon': function(size) {
+ return pref.create_icon_texture(size);
+ }
+ };
},
getInitialResultSet: function(terms) {
- return this._appSys.initial_search(false, terms);
+ return this._appSys.search_settings(terms);
},
getSubsearchResultSet: function(previousResults, terms) {
- return this._appSys.subsearch(false, previousResults, terms);
+ return this._appSys.search_settings(terms);
},
- createResultActor: function (resultMeta, terms) {
- let app = this._appSys.get_app(resultMeta['id']);
- let icon = new AppWellIcon(app);
- return icon.actor;
- }
-};
-
-function PrefsSearchProvider() {
- this._init();
-}
-
-PrefsSearchProvider.prototype = {
- __proto__: BaseAppSearchProvider.prototype,
+ activateResult: function(pref, params) {
+ params = Params.parse(params, { workspace: null,
+ timestamp: null });
- _init: function() {
- BaseAppSearchProvider.prototype._init.call(this, _("SETTINGS"));
+ pref.activate(params.workspace);
},
- getInitialResultSet: function(terms) {
- return this._appSys.initial_search(true, terms);
+ dragActivateResult: function(pref, params) {
+ this.activateResult(pref, params);
},
- getSubsearchResultSet: function(previousResults, terms) {
- return this._appSys.subsearch(true, previousResults, terms);
+ createResultActor: function (resultMeta, terms) {
+ let app = resultMeta['id'];
+ let icon = new AppWellIcon(app);
+ return icon.actor;
}
};
}
};
-function AppWellIcon(app, iconParams) {
- this._init(app, iconParams);
+function AppWellIcon(app, iconParams, onActivateOverride) {
+ this._init(app, iconParams, onActivateOverride);
}
AppWellIcon.prototype = {
- _init : function(app, iconParams) {
+ _init : function(app, iconParams, onActivateOverride) {
this.app = app;
this.actor = new St.Button({ style_class: 'app-well-app',
reactive: true,
this.actor.label_actor = this.icon.label;
+ // A function callback to override the default "app.activate()"; used by preferences
+ this._onActivateOverride = onActivateOverride;
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
this.actor.connect('popup-menu', Lang.bind(this, this._onKeyboardPopupMenu));
this.emit('launching');
let modifiers = Shell.get_event_state(event);
- if (modifiers & Clutter.ModifierType.CONTROL_MASK
- && this.app.state == Shell.AppState.RUNNING) {
- this.app.open_new_window(-1);
+ if (this._onActivateOverride) {
+ this._onActivateOverride(event);
} else {
- this.app.activate(-1);
+ if (modifiers & Clutter.ModifierType.CONTROL_MASK
+ && this.app.state == Shell.AppState.RUNNING) {
+ this.app.open_new_window(-1);
+ } else {
+ this.app.activate(-1);
+ }
}
Main.overview.hide();
},
#include "shell-global.h"
#include "st.h"
-#define GMENU_I_KNOW_THIS_IS_UNSTABLE
-#include <gmenu-tree.h>
-
/* Vendor prefixes are something that can be preprended to a .desktop
* file name. Undo this.
*/
struct _ShellAppSystemPrivate {
GMenuTree *apps_tree;
- GMenuTree *settings_tree;
- GHashTable *app_id_to_info;
- GHashTable *app_id_to_app;
+ GHashTable *entry_to_app;
- GSList *cached_flattened_apps; /* ShellAppInfo */
- GSList *cached_settings; /* ShellAppInfo */
GSList *known_vendor_prefixes;
- gboolean loaded;
- gint app_monitor_id;
-
- guint app_change_timeout_id;
+ GMenuTree *settings_tree;
+ GHashTable *setting_entry_to_app;
};
-static char *shell_app_info_get_prefix (ShellAppInfo *info);
static void shell_app_system_finalize (GObject *object);
-static gboolean on_tree_changed (gpointer user_data);
-static void on_tree_changed_cb (GMenuTree *tree, gpointer user_data);
-static void reread_menus (ShellAppSystem *self);
+static void on_apps_tree_changed_cb (GMenuTree *tree, gpointer user_data);
+static void on_settings_tree_changed_cb (GMenuTree *tree, gpointer user_data);
G_DEFINE_TYPE(ShellAppSystem, shell_app_system, G_TYPE_OBJECT);
-typedef enum {
- SHELL_APP_INFO_TYPE_ENTRY,
- SHELL_APP_INFO_TYPE_DESKTOP_FILE,
- SHELL_APP_INFO_TYPE_WINDOW
-} ShellAppInfoType;
-
-struct _ShellAppInfo {
- ShellAppInfoType type;
-
- /* We need this for two reasons. First, GKeyFile doesn't have a refcount.
- * http://bugzilla.gnome.org/show_bug.cgi?id=590808
- *
- * But more generally we'll always need it so we know when to free this
- * structure (short of weak references on each item).
- */
- guint refcount;
-
- char *casefolded_name;
- char *name_collation_key;
- char *casefolded_description;
- char *casefolded_exec;
-
- GMenuTreeEntry *entry;
-
- GKeyFile *keyfile;
- char *keyfile_path;
-
- MetaWindow *window;
- char *window_id;
-};
-
-ShellAppInfo*
-shell_app_info_ref (ShellAppInfo *info)
-{
- info->refcount++;
- return info;
-}
-
-void
-shell_app_info_unref (ShellAppInfo *info)
-{
- if (--info->refcount > 0)
- return;
-
- g_free (info->casefolded_name);
- g_free (info->name_collation_key);
- g_free (info->casefolded_description);
-
- switch (info->type)
- {
- case SHELL_APP_INFO_TYPE_ENTRY:
- gmenu_tree_item_unref (info->entry);
- break;
- case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
- g_key_file_free (info->keyfile);
- g_free (info->keyfile_path);
- break;
- case SHELL_APP_INFO_TYPE_WINDOW:
- g_object_unref (info->window);
- g_free (info->window_id);
- break;
- }
- g_slice_free (ShellAppInfo, info);
-}
-
-static ShellAppInfo *
-shell_app_info_new_from_tree_item (GMenuTreeEntry *entry)
-{
- ShellAppInfo *info;
-
- if (!entry)
- return NULL;
-
- info = g_slice_alloc0 (sizeof (ShellAppInfo));
- info->type = SHELL_APP_INFO_TYPE_ENTRY;
- info->refcount = 1;
- info->entry = (GMenuTreeEntry*)gmenu_tree_item_ref (entry);
- return info;
-}
-
-static ShellAppInfo *
-shell_app_info_new_from_window (MetaWindow *window)
-{
- ShellAppInfo *info;
-
- info = g_slice_alloc0 (sizeof (ShellAppInfo));
- info->type = SHELL_APP_INFO_TYPE_WINDOW;
- info->refcount = 1;
- info->window = g_object_ref (window);
- /* For windows, its id is simply its pointer address as a string.
- * There are various other alternatives, but the address is unique
- * and unchanging, which is pretty much the best we can do.
- */
- info->window_id = g_strdup_printf ("window:%p", window);
- return info;
-}
-
static void shell_app_system_class_init(ShellAppSystemClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *)klass;
SHELL_TYPE_APP_SYSTEM,
ShellAppSystemPrivate);
- /* The key is owned by the value */
- priv->app_id_to_info = g_hash_table_new_full (g_str_hash, g_str_equal,
- NULL, (GDestroyNotify) shell_app_info_unref);
-
- /* Key is owned by info */
- priv->app_id_to_app = g_hash_table_new (g_str_hash, g_str_equal);
+ priv->entry_to_app = g_hash_table_new_full (NULL, NULL,
+ (GDestroyNotify)gmenu_tree_item_unref,
+ (GDestroyNotify)g_object_unref);
+ priv->setting_entry_to_app = g_hash_table_new_full (NULL, NULL,
+ (GDestroyNotify)gmenu_tree_item_unref,
+ (GDestroyNotify)g_object_unref);
/* For now, we want to pick up Evince, Nautilus, etc. We'll
* handle NODISPLAY semantics at a higher level or investigate them
* case by case.
*/
priv->apps_tree = gmenu_tree_new ("applications.menu", GMENU_TREE_FLAGS_INCLUDE_NODISPLAY);
- priv->settings_tree = gmenu_tree_new ("gnomecc.menu", GMENU_TREE_FLAGS_INCLUDE_NODISPLAY);
-
- priv->app_change_timeout_id = 0;
+ g_signal_connect (priv->apps_tree, "changed", G_CALLBACK (on_apps_tree_changed_cb), self);
- g_signal_connect (priv->apps_tree, "changed", G_CALLBACK (on_tree_changed_cb), self);
- g_signal_connect (priv->settings_tree, "changed", G_CALLBACK (on_tree_changed_cb), self);
+ priv->settings_tree = gmenu_tree_new ("gnomecc.menu", 0);
+ g_signal_connect (priv->settings_tree, "changed", G_CALLBACK (on_settings_tree_changed_cb), self);
- reread_menus (self);
+ on_apps_tree_changed_cb (priv->apps_tree, self);
+ on_settings_tree_changed_cb (priv->settings_tree, self);
}
static void
g_object_unref (priv->apps_tree);
g_object_unref (priv->settings_tree);
- g_hash_table_destroy (priv->app_id_to_info);
- g_hash_table_destroy (priv->app_id_to_app);
-
- g_slist_foreach (priv->cached_flattened_apps, (GFunc)shell_app_info_unref, NULL);
- g_slist_free (priv->cached_flattened_apps);
- priv->cached_flattened_apps = NULL;
+ g_hash_table_destroy (priv->entry_to_app);
+ g_hash_table_destroy (priv->setting_entry_to_app);
g_slist_foreach (priv->known_vendor_prefixes, (GFunc)g_free, NULL);
g_slist_free (priv->known_vendor_prefixes);
priv->known_vendor_prefixes = NULL;
- g_slist_foreach (priv->cached_settings, (GFunc)shell_app_info_unref, NULL);
- g_slist_free (priv->cached_settings);
- priv->cached_settings = NULL;
+ G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
+}
+
+static char *
+get_prefix_for_entry (GMenuTreeEntry *entry)
+{
+ char *prefix = NULL, *file_prefix = NULL;
+ const char *id;
+ GFile *file;
+ char *name;
+ int i = 0;
+
+ id = gmenu_tree_entry_get_desktop_file_id (entry);
+ file = g_file_new_for_path (gmenu_tree_entry_get_desktop_file_path (entry));
+ name = g_file_get_basename (file);
+
+ if (!name)
+ {
+ g_object_unref (file);
+ return NULL;
+ }
+ for (i = 0; vendor_prefixes[i]; i++)
+ {
+ if (g_str_has_prefix (name, vendor_prefixes[i]))
+ {
+ file_prefix = g_strdup (vendor_prefixes[i]);
+ break;
+ }
+ }
+
+ while (strcmp (name, id) != 0)
+ {
+ char *t;
+ char *pname;
+ GFile *parent = g_file_get_parent (file);
+
+ if (!parent)
+ {
+ g_warn_if_reached ();
+ break;
+ }
+
+ pname = g_file_get_basename (parent);
+ if (!pname)
+ {
+ g_object_unref (parent);
+ break;
+ }
+ if (!g_strstr_len (id, -1, pname))
+ {
+ /* handle <LegacyDir prefix="..."> */
+ char *t;
+ size_t name_len = strlen (name);
+ size_t id_len = strlen (id);
+ char *t_id = g_strdup (id);
+
+ t_id[id_len - name_len] = '\0';
+ t = g_strdup(t_id);
+ g_free (prefix);
+ g_free (t_id);
+ g_free (name);
+ name = g_strdup (id);
+ prefix = t;
+
+ g_object_unref (file);
+ file = parent;
+ g_free (pname);
+ g_free (file_prefix);
+ file_prefix = NULL;
+ break;
+ }
+
+ t = g_strconcat (pname, "-", name, NULL);
+ g_free (name);
+ name = t;
+
+ t = g_strconcat (pname, "-", prefix, NULL);
+ g_free (prefix);
+ prefix = t;
+
+ g_object_unref (file);
+ file = parent;
+ g_free (pname);
+ }
+
+ if (file)
+ g_object_unref (file);
+
+ if (strcmp (name, id) == 0)
+ {
+ g_free (name);
+ if (file_prefix && !prefix)
+ return file_prefix;
+ if (file_prefix)
+ {
+ char *t = g_strconcat (prefix, "-", file_prefix, NULL);
+ g_free (prefix);
+ g_free (file_prefix);
+ prefix = t;
+ }
+ return prefix;
+ }
+
+ g_free (name);
+ g_free (prefix);
+ g_free (file_prefix);
+ g_return_val_if_reached (NULL);
+}
+
+static void
+load_app_entry (ShellAppSystem *self,
+ GMenuTreeEntry *entry)
+{
+ char *prefix;
+ ShellApp *app;
+
+ if (g_hash_table_lookup (self->priv->entry_to_app, entry))
+ return;
+
+ prefix = get_prefix_for_entry (entry);
+
+ if (prefix
+ && !g_slist_find_custom (self->priv->known_vendor_prefixes, prefix,
+ (GCompareFunc)g_strcmp0))
+ self->priv->known_vendor_prefixes = g_slist_append (self->priv->known_vendor_prefixes,
+ prefix);
+ else
+ g_free (prefix);
- G_OBJECT_CLASS (shell_app_system_parent_class)->finalize(object);
+ app = _shell_app_new (entry);
+
+ g_hash_table_insert (self->priv->entry_to_app, gmenu_tree_item_ref (entry), app);
}
-static GSList *
-gather_entries_recurse (ShellAppSystem *monitor,
- GSList *apps,
- GHashTable *unique,
- GMenuTreeDirectory *root)
+static void
+gather_apps_recurse (ShellAppSystem *self,
+ GMenuTreeDirectory *root)
{
GMenuTreeIter *iter = gmenu_tree_directory_iter (root);
GMenuTreeItemType next_type;
switch (next_type)
{
- case GMENU_TREE_ITEM_INVALID:
- break;
case GMENU_TREE_ITEM_ENTRY:
{
- ShellAppInfo *app;
item = gmenu_tree_iter_get_entry (iter);
- app = shell_app_info_new_from_tree_item (item);
- if (!g_hash_table_lookup (unique, shell_app_info_get_id (app)))
- {
- apps = g_slist_prepend (apps, app);
- g_hash_table_insert (unique, (char*)shell_app_info_get_id (app), app);
- }
+ load_app_entry (self, (GMenuTreeEntry*)item);
}
break;
case GMENU_TREE_ITEM_DIRECTORY:
{
item = gmenu_tree_iter_get_directory (iter);
- apps = gather_entries_recurse (monitor, apps, unique, (GMenuTreeDirectory*)item);
+ gather_apps_recurse (self, (GMenuTreeDirectory*)item);
}
break;
default:
break;
}
- gmenu_tree_item_unref (item);
+ if (item != NULL)
+ gmenu_tree_item_unref (item);
}
gmenu_tree_iter_unref (iter);
-
- return apps;
}
static void
-reread_entries (ShellAppSystem *self,
- GSList **cache,
- GHashTable *unique,
- GMenuTree *tree)
+gather_settings_recurse (ShellAppSystem *self,
+ GMenuTreeDirectory *root)
{
- GMenuTreeDirectory *trunk;
-
- trunk = gmenu_tree_get_root_directory (tree);
-
- g_slist_foreach (*cache, (GFunc)shell_app_info_unref, NULL);
- g_slist_free (*cache);
- *cache = NULL;
+ GMenuTreeIter *iter = gmenu_tree_directory_iter (root);
+ GMenuTreeItemType next_type;
- if (!trunk)
- {
- *cache = NULL;
- }
- else
+ while ((next_type = gmenu_tree_iter_next (iter)) != GMENU_TREE_ITEM_INVALID)
{
- *cache = gather_entries_recurse (self, *cache, unique, trunk);
- gmenu_tree_item_unref (trunk);
- }
-}
+ gpointer item = NULL;
-static void
-cache_by_id (ShellAppSystem *self, GSList *apps)
-{
- GSList *iter;
+ switch (next_type)
+ {
+ case GMENU_TREE_ITEM_ENTRY:
+ {
+ ShellApp *app;
- for (iter = apps; iter; iter = iter->next)
- {
- ShellAppInfo *info = iter->data;
- const char *id = shell_app_info_get_id (info);
- char *prefix = shell_app_info_get_prefix (info);
-
- shell_app_info_ref (info);
- /* the name is owned by the info itself */
-
- if (prefix
- && !g_slist_find_custom (self->priv->known_vendor_prefixes, prefix,
- (GCompareFunc)g_strcmp0))
- self->priv->known_vendor_prefixes = g_slist_append (self->priv->known_vendor_prefixes,
- prefix);
- else
- g_free (prefix);
- g_hash_table_replace (self->priv->app_id_to_info, (char*)id, info);
+ item = gmenu_tree_iter_get_entry (iter);
+ if (g_hash_table_lookup (self->priv->setting_entry_to_app, item))
+ return;
+
+ app = _shell_app_new (item);
+
+ g_hash_table_insert (self->priv->setting_entry_to_app, gmenu_tree_item_ref (item), app);
+ }
+ break;
+ case GMENU_TREE_ITEM_DIRECTORY:
+ {
+ item = gmenu_tree_iter_get_directory (iter);
+ gather_settings_recurse (self, (GMenuTreeDirectory*)item);
+ }
+ break;
+ default:
+ break;
+ }
+ if (item != NULL)
+ gmenu_tree_item_unref (item);
}
+
+ gmenu_tree_iter_unref (iter);
}
static void
-reread_menus (ShellAppSystem *self)
+on_apps_tree_changed_cb (GMenuTree *tree,
+ gpointer user_data)
{
- GHashTable *unique;
+ ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
GError *error = NULL;
+ GMenuTreeDirectory *root;
- if (!self->priv->loaded)
- {
- if (!gmenu_tree_load_sync (self->priv->apps_tree, &error))
- {
- g_warning ("Failed to load apps: %s", error->message);
- return;
- }
- if (!gmenu_tree_load_sync (self->priv->settings_tree, &error))
- {
- g_warning ("Failed to load settings: %s", error->message);
- return;
- }
- self->priv->loaded = TRUE;
- }
-
- unique = g_hash_table_new (g_str_hash, g_str_equal);
+ g_assert (tree == self->priv->apps_tree);
+ g_hash_table_remove_all (self->priv->entry_to_app);
g_slist_foreach (self->priv->known_vendor_prefixes, (GFunc)g_free, NULL);
g_slist_free (self->priv->known_vendor_prefixes);
self->priv->known_vendor_prefixes = NULL;
- reread_entries (self, &(self->priv->cached_flattened_apps), unique, self->priv->apps_tree);
- g_hash_table_remove_all (unique);
- reread_entries (self, &(self->priv->cached_settings), unique, self->priv->settings_tree);
- g_hash_table_destroy (unique);
-
- g_hash_table_remove_all (self->priv->app_id_to_info);
-
- cache_by_id (self, self->priv->cached_flattened_apps);
- cache_by_id (self, self->priv->cached_settings);
-}
+ if (!gmenu_tree_load_sync (self->priv->apps_tree, &error))
+ {
+ g_warning ("Failed to load apps: %s", error->message);
+ return;
+ }
-static gboolean
-on_tree_changed (gpointer user_data)
-{
- ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
+ root = gmenu_tree_get_root_directory (self->priv->apps_tree);
- self->priv->loaded = FALSE;
- reread_menus (self);
+ if (root)
+ {
+ gather_apps_recurse (self, root);
+ gmenu_tree_item_unref (root);
+ }
g_signal_emit (self, signals[INSTALLED_CHANGED], 0);
-
- self->priv->app_change_timeout_id = 0;
- return FALSE;
}
static void
-on_tree_changed_cb (GMenuTree *monitor, gpointer user_data)
+on_settings_tree_changed_cb (GMenuTree *tree,
+ gpointer user_data)
{
ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
+ GError *error = NULL;
+ GMenuTreeDirectory *root;
- /* GMenu currently gives us a separate notification on the entire
- * menu tree for each node in the tree that might potentially have
- * changed. (See http://bugzilla.gnome.org/show_bug.cgi?id=172046.)
- * We need to compress these to avoid doing large extra amounts of
- * work.
- *
- * Even when that bug is fixed, compression is still useful; for one
- * thing we want to need to compress across notifications of changes
- * to the settings tree. Second we want to compress if multiple
- * changes are made to the desktop files at different times but in
- * short succession.
- */
+ g_assert (tree == self->priv->settings_tree);
- if (self->priv->app_change_timeout_id != 0)
- return;
- self->priv->app_change_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 3000,
- (GSourceFunc) on_tree_changed,
- self, NULL);
-}
+ g_hash_table_remove_all (self->priv->setting_entry_to_app);
+ if (!gmenu_tree_load_sync (self->priv->settings_tree, &error))
+ {
+ g_warning ("Failed to load settings: %s", error->message);
+ return;
+ }
-GType
-shell_app_info_get_type (void)
-{
- static GType gtype = G_TYPE_INVALID;
- if (gtype == G_TYPE_INVALID)
+ root = gmenu_tree_get_root_directory (self->priv->settings_tree);
+
+ if (root)
{
- gtype = g_boxed_type_register_static ("ShellAppInfo",
- (GBoxedCopyFunc)shell_app_info_ref,
- (GBoxedFreeFunc)shell_app_info_unref);
+ gather_settings_recurse (self, root);
+ gmenu_tree_item_unref (root);
}
- return gtype;
}
/**
- * shell_app_system_get_flattened_apps:
+ * shell_app_system_get_tree:
*
- * Traverses a toplevel menu, and returns all items under it. Nested items
- * are flattened. This value is computed on initial call and cached thereafter
- * until the set of installed applications changes.
- *
- * Return value: (transfer none) (element-type ShellAppInfo): List of applications
+ * Return Value: (transfer none): The #GMenuTree for apps
*/
-GSList *
-shell_app_system_get_flattened_apps (ShellAppSystem *self)
+GMenuTree *
+shell_app_system_get_tree (ShellAppSystem *self)
{
- return self->priv->cached_flattened_apps;
+ return self->priv->apps_tree;
}
/**
- * shell_app_system_get_all_settings:
+ * shell_app_system_get_settings_tree:
*
- * Returns a list of application items under "settings.menu".
+ * Return Value: (transfer none): The #GMenuTree for apps
+ */
+GMenuTree *
+shell_app_system_get_settings_tree (ShellAppSystem *self)
+{
+ return self->priv->settings_tree;
+}
+
+/**
+ * shell_app_system_lookup_setting:
+ * @self:
+ * @id: desktop file id
*
- * Return value: (transfer none) (element-type ShellAppInfo): List of applications
+ * Returns: (transfer none): Application in gnomecc.menu, or %NULL if none
*/
-GSList *
-shell_app_system_get_all_settings (ShellAppSystem *monitor)
+ShellApp *
+shell_app_system_lookup_setting (ShellAppSystem *self,
+ const char *id)
{
- return monitor->priv->cached_settings;
+ GMenuTreeEntry *entry;
+ ShellApp *app;
+
+ /* Actually defer to the main app set if there's overlap */
+ app = shell_app_system_lookup_app (self, id);
+ if (app != NULL)
+ return app;
+
+ entry = gmenu_tree_get_entry_by_id (self->priv->settings_tree, id);
+ if (entry == NULL)
+ return NULL;
+
+ app = g_hash_table_lookup (self->priv->setting_entry_to_app, entry);
+ if (app != NULL)
+ return app;
+
+ app = _shell_app_new (entry);
+ g_hash_table_insert (self->priv->setting_entry_to_app, gmenu_tree_item_ref (entry), app);
+
+ return app;
}
/**
return instance;
}
-typedef struct {
- ShellAppSystem *appsys;
- ShellAppInfo *info;
-} ShellAppRef;
-
-static void
-shell_app_system_on_app_weakref (gpointer data,
- GObject *location)
-{
- ShellAppRef *ref = data;
-
- g_hash_table_remove (ref->appsys->priv->app_id_to_app, shell_app_info_get_id (ref->info));
- shell_app_info_unref (ref->info);
- g_free (ref);
-}
-
/**
- * shell_app_system_get_app:
+ * shell_app_system_lookup_app:
*
- * Find or create a #ShellApp corresponding to an id; if already cached
- * elsewhere in memory, return that instance. Otherwise, create a new
- * one.
+ * Find a #ShellApp corresponding to an id.
*
- * Return value: (transfer full): The #ShellApp for id, or %NULL if none
+ * Return value: (transfer none): The #ShellApp for id, or %NULL if none
*/
ShellApp *
-shell_app_system_get_app (ShellAppSystem *self,
- const char *id)
+shell_app_system_lookup_app (ShellAppSystem *self,
+ const char *id)
{
- ShellAppInfo *info;
- ShellApp *app;
-
- app = g_hash_table_lookup (self->priv->app_id_to_app, id);
- if (app)
- return g_object_ref (app);
+ GMenuTreeEntry *entry;
- info = g_hash_table_lookup (self->priv->app_id_to_info, id);
- if (!info)
+ entry = gmenu_tree_get_entry_by_id (self->priv->apps_tree, id);
+ if (entry == NULL)
return NULL;
- app = _shell_app_new (info);
+ return g_hash_table_lookup (self->priv->entry_to_app, entry);
+}
- return app;
+/**
+ * shell_app_system_lookup_app_by_tree_entry:
+ * @system: a #ShellAppSystem
+ * @entry: a #GMenuTreeEntry
+ *
+ * Find a #ShellApp corresponding to a #GMenuTreeEntry.
+ *
+ * Return value: (transfer none): The #ShellApp for @entry, or %NULL if none
+ */
+ShellApp *
+shell_app_system_lookup_app_by_tree_entry (ShellAppSystem *self,
+ GMenuTreeEntry *entry)
+{
+ return g_hash_table_lookup (self->priv->entry_to_app, entry);
}
/**
- * shell_app_system_get_app_for_path:
+ * shell_app_system_lookup_app_for_path:
* @system: a #ShellAppSystem
* @desktop_path: (type utf8): UTF-8 encoded absolute file name
*
- * Find or create a #ShellApp corresponding to a given absolute
- * file name which must be in the standard paths (XDG_DATA_DIRS).
- * For files outside the datadirs, this function returns %NULL.
+ * Find or create a #ShellApp corresponding to a given absolute file
+ * name which must be in the standard paths (XDG_DATA_DIRS). For
+ * files outside the datadirs, this function returns %NULL.
*
- * If already cached elsewhere in memory, return that instance.
- * Otherwise, create a new one.
- *
- * Return value: (transfer full): The #ShellApp for id, or %NULL if none
+ * Return value: (transfer none): The #ShellApp for id, or %NULL if none
*/
ShellApp *
-shell_app_system_get_app_for_path (ShellAppSystem *system,
- const char *desktop_path)
+shell_app_system_lookup_app_for_path (ShellAppSystem *system,
+ const char *desktop_path)
{
const char *basename;
- ShellAppInfo *info;
+ const char *app_path;
+ ShellApp *app;
basename = g_strrstr (desktop_path, "/");
if (basename)
else
basename = desktop_path;
- info = g_hash_table_lookup (system->priv->app_id_to_info, basename);
- if (!info)
+ app = shell_app_system_lookup_app (system, basename);
+ if (!app)
return NULL;
- if (info->type == SHELL_APP_INFO_TYPE_ENTRY)
- {
- const char *full_path = gmenu_tree_entry_get_desktop_file_path (info->entry);
- if (strcmp (desktop_path, full_path) != 0)
- return NULL;
- }
- else
+ app_path = gmenu_tree_entry_get_desktop_file_path (shell_app_get_tree_entry (app));
+ if (strcmp (desktop_path, app_path) != 0)
return NULL;
- return shell_app_system_get_app (system, basename);
-}
-
-/**
- * shell_app_system_get_app_for_window:
- * @self: A #ShellAppSystem
- * @window: A #MetaWindow
- *
- * Find or create a #ShellApp for window
- *
- * Return value: (transfer full): The #ShellApp for window, or %NULL if none
- */
-ShellApp *
-shell_app_system_get_app_for_window (ShellAppSystem *self,
- MetaWindow *window)
-{
- char *id = g_strdup_printf ("window:%p", window);
- ShellApp *app = g_hash_table_lookup (self->priv->app_id_to_app, id);
-
- if (app)
- g_object_ref (G_OBJECT (app));
- else
- app = _shell_app_new_for_window (window);
-
- g_free (id);
-
- return app;
-}
-
-/* ShellAppSystem ensures we have a unique instance of
- * apps per id.
- */
-void
-_shell_app_system_register_app (ShellAppSystem *self,
- ShellApp *app)
-{
- const char *id;
- ShellAppRef *ref;
-
- id = shell_app_get_id (app);
-
- g_return_if_fail (g_hash_table_lookup (self->priv->app_id_to_app, id) == NULL);
-
- ref = g_new0 (ShellAppRef, 1);
- ref->appsys = self;
- ref->info = shell_app_info_ref (_shell_app_get_info (app));
- g_hash_table_insert (self->priv->app_id_to_app, (char*)shell_app_info_get_id (ref->info), app);
- g_object_weak_ref (G_OBJECT (app), shell_app_system_on_app_weakref, ref);
-}
-
-/**
- * shell_app_system_create_from_window:
- *
- * In the case where we can't otherwise determine an application
- * associated with a window, this function can create a "fake"
- * application just backed by information from the window itself.
- *
- * Return value: (transfer full): A new #ShellAppInfo
- */
-ShellAppInfo *
-shell_app_system_create_from_window (ShellAppSystem *system, MetaWindow *window)
-{
- return shell_app_info_new_from_window (window);
+ return app;
}
/**
* heuristically determined application identifier
* string, or %NULL if none.
*
- * Returns: (transfer full): A #ShellApp for @name
+ * Returns: (transfer none): A #ShellApp for @name
*/
ShellApp *
shell_app_system_lookup_heuristic_basename (ShellAppSystem *system,
- const char *name)
+ const char *name)
{
ShellApp *result;
GSList *prefix;
- result = shell_app_system_get_app (system, name);
+
+ result = shell_app_system_lookup_app (system, name);
if (result != NULL)
return result;
+
for (prefix = system->priv->known_vendor_prefixes; prefix; prefix = g_slist_next (prefix))
{
char *tmpid = g_strconcat ((char*)prefix->data, name, NULL);
- result = shell_app_system_get_app (system, tmpid);
+ result = shell_app_system_lookup_app (system, tmpid);
g_free (tmpid);
if (result != NULL)
return result;
return NULL;
}
-typedef enum {
- MATCH_NONE,
- MATCH_SUBSTRING, /* Not prefix, substring */
- MATCH_MULTIPLE_SUBSTRING, /* Matches multiple criteria with substrings */
- MATCH_PREFIX, /* Strict prefix */
- MATCH_MULTIPLE_PREFIX, /* Matches multiple criteria, at least one prefix */
-} ShellAppInfoSearchMatch;
-
-static char *
-normalize_and_casefold (const char *str)
-{
- char *normalized, *result;
-
- if (str == NULL)
- return NULL;
-
- normalized = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
- result = g_utf8_casefold (normalized, -1);
- g_free (normalized);
- return result;
-}
-
-static char *
-trim_exec_line (const char *str)
-{
- const char *start, *end, *pos;
-
- end = strchr (str, ' ');
- if (end == NULL)
- end = str + strlen (str);
-
- start = str;
- while ((pos = strchr (start, '/')) && pos < end)
- start = ++pos;
-
- return g_strndup (start, end - start);
-}
-
-static void
-shell_app_info_init_search_data (ShellAppInfo *info)
-{
- const char *name;
- const char *exec;
- const char *comment;
- char *normalized_exec;
- GDesktopAppInfo *appinfo;
-
- g_assert (info->type == SHELL_APP_INFO_TYPE_ENTRY);
-
- appinfo = gmenu_tree_entry_get_app_info (info->entry);
- name = g_app_info_get_name (G_APP_INFO (appinfo));
- info->casefolded_name = normalize_and_casefold (name);
-
- comment = g_app_info_get_description (G_APP_INFO (appinfo));
- info->casefolded_description = normalize_and_casefold (comment);
-
- exec = g_app_info_get_executable (G_APP_INFO (appinfo));
- normalized_exec = normalize_and_casefold (exec);
- info->casefolded_exec = trim_exec_line (normalized_exec);
- g_free (normalized_exec);
-}
-
-static ShellAppInfoSearchMatch
-shell_app_info_match_terms (ShellAppInfo *info,
- GSList *terms)
+/**
+ * shell_app_system_get_all:
+ * @self:
+ *
+ * Returns: (transfer container) (element-type ShellApp): All installed applications
+ */
+GSList *
+shell_app_system_get_all (ShellAppSystem *self)
{
- GSList *iter;
- ShellAppInfoSearchMatch match;
-
- if (G_UNLIKELY(!info->casefolded_name))
- shell_app_info_init_search_data (info);
+ GSList *result = NULL;
+ GHashTableIter iter;
+ gpointer key, value;
- match = MATCH_NONE;
- for (iter = terms; iter; iter = iter->next)
+ g_hash_table_iter_init (&iter, self->priv->entry_to_app);
+ while (g_hash_table_iter_next (&iter, &key, &value))
{
- ShellAppInfoSearchMatch current_match;
- const char *term = iter->data;
- const char *p;
-
- current_match = MATCH_NONE;
-
- p = strstr (info->casefolded_name, term);
- if (p == info->casefolded_name)
- current_match = MATCH_PREFIX;
- else if (p != NULL)
- current_match = MATCH_SUBSTRING;
-
- p = strstr (info->casefolded_exec, term);
- if (p != NULL)
- {
- if (p == info->casefolded_exec)
- current_match = (current_match == MATCH_NONE) ? MATCH_PREFIX
- : MATCH_MULTIPLE_PREFIX;
- else if (current_match < MATCH_PREFIX)
- current_match = (current_match == MATCH_NONE) ? MATCH_SUBSTRING
- : MATCH_MULTIPLE_SUBSTRING;
- }
-
- if (info->casefolded_description && current_match < MATCH_PREFIX)
- {
- /* Only do substring matches, as prefix matches are not meaningful
- * enough for descriptions
- */
- p = strstr (info->casefolded_description, term);
- if (p != NULL)
- current_match = (current_match == MATCH_NONE) ? MATCH_SUBSTRING
- : MATCH_MULTIPLE_SUBSTRING;
- }
-
- if (current_match == MATCH_NONE)
- return current_match;
-
- if (current_match > match)
- match = current_match;
+ ShellApp *app = value;
+ result = g_slist_prepend (result, app);
}
- return match;
+ return result;
}
static gint
-shell_app_info_compare (gconstpointer a,
- gconstpointer b,
- gpointer data)
+compare_apps_by_name (gconstpointer a,
+ gconstpointer b,
+ gpointer data)
{
- ShellAppSystem *system = data;
- const char *id_a = a;
- const char *id_b = b;
- ShellAppInfo *info_a = g_hash_table_lookup (system->priv->app_id_to_info, id_a);
- ShellAppInfo *info_b = g_hash_table_lookup (system->priv->app_id_to_info, id_b);
- GDesktopAppInfo *app_info_a = gmenu_tree_entry_get_app_info (info_a->entry);
- GDesktopAppInfo *app_info_b = gmenu_tree_entry_get_app_info (info_b->entry);
-
- if (!info_a->name_collation_key)
- info_a->name_collation_key = g_utf8_collate_key (g_app_info_get_name ((GAppInfo*)app_info_a), -1);
- if (!info_b->name_collation_key)
- info_b->name_collation_key = g_utf8_collate_key (g_app_info_get_name ((GAppInfo*)app_info_b), -1);
-
- return strcmp (info_a->name_collation_key, info_b->name_collation_key);
+ ShellApp *app_a = (ShellApp*)a;
+ ShellApp *app_b = (ShellApp*)b;
+
+ return shell_app_compare_by_name (app_a, app_b);
}
static GSList *
GSList *substring_matches)
{
multiple_prefix_matches = g_slist_sort_with_data (multiple_prefix_matches,
- shell_app_info_compare,
+ compare_apps_by_name,
system);
prefix_matches = g_slist_sort_with_data (prefix_matches,
- shell_app_info_compare,
+ compare_apps_by_name,
system);
multiple_substring_matches = g_slist_sort_with_data (multiple_substring_matches,
- shell_app_info_compare,
+ compare_apps_by_name,
system);
substring_matches = g_slist_sort_with_data (substring_matches,
- shell_app_info_compare,
+ compare_apps_by_name,
system);
return g_slist_concat (multiple_prefix_matches, g_slist_concat (prefix_matches, g_slist_concat (multiple_substring_matches, substring_matches)));
}
+static char *
+normalize_and_casefold (const char *str)
+{
+ char *normalized, *result;
+
+ if (str == NULL)
+ return NULL;
+
+ normalized = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
+ result = g_utf8_casefold (normalized, -1);
+ g_free (normalized);
+ return result;
+}
+
/**
* normalize_terms:
* @terms: (element-type utf8): Input search terms
return normalized_terms;
}
-static inline void
-shell_app_system_do_match (ShellAppSystem *system,
- ShellAppInfo *info,
- GSList *terms,
- GSList **multiple_prefix_results,
- GSList **prefix_results,
- GSList **multiple_substring_results,
- GSList **substring_results)
-{
- const char *id = shell_app_info_get_id (info);
- ShellAppInfoSearchMatch match;
-
- if (shell_app_info_get_is_nodisplay (info))
- return;
-
- match = shell_app_info_match_terms (info, terms);
- switch (match)
- {
- case MATCH_NONE:
- break;
- case MATCH_MULTIPLE_PREFIX:
- *multiple_prefix_results = g_slist_prepend (*multiple_prefix_results,
- (char *) id);
- break;
- case MATCH_PREFIX:
- *prefix_results = g_slist_prepend (*prefix_results, (char *) id);
- break;
- case MATCH_MULTIPLE_SUBSTRING:
- *multiple_substring_results = g_slist_prepend (*multiple_substring_results,
- (char *) id);
- break;
- case MATCH_SUBSTRING:
- *substring_results = g_slist_prepend (*substring_results, (char *) id);
- break;
- }
-}
-
static GSList *
-shell_app_system_initial_search_internal (ShellAppSystem *self,
- GSList *terms,
- GSList *source)
+search_tree (ShellAppSystem *self,
+ GSList *terms,
+ GHashTable *apps)
{
GSList *multiple_prefix_results = NULL;
GSList *prefix_results = NULL;
GSList *multiple_subtring_results = NULL;
GSList *substring_results = NULL;
- GSList *iter;
- GSList *normalized_terms = normalize_terms (terms);
+ GSList *normalized_terms;
+ GHashTableIter iter;
+ gpointer key, value;
- for (iter = source; iter; iter = iter->next)
- {
- ShellAppInfo *info = iter->data;
+ normalized_terms = normalize_terms (terms);
- shell_app_system_do_match (self, info, normalized_terms,
- &multiple_prefix_results, &prefix_results,
- &multiple_subtring_results, &substring_results);
+ g_hash_table_iter_init (&iter, apps);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const char *id = key;
+ ShellApp *app = value;
+ (void)id;
+ _shell_app_do_match (app, normalized_terms,
+ &multiple_prefix_results, &prefix_results,
+ &multiple_subtring_results, &substring_results);
}
g_slist_foreach (normalized_terms, (GFunc)g_free, NULL);
g_slist_free (normalized_terms);
- return sort_and_concat_results (self, multiple_prefix_results, prefix_results, multiple_subtring_results, substring_results);
+ return sort_and_concat_results (self, multiple_prefix_results, prefix_results,
+ multiple_subtring_results, substring_results);
+
}
/**
* shell_app_system_initial_search:
* @system: A #ShellAppSystem
- * @prefs: %TRUE if we should search preferences instead of apps
* @terms: (element-type utf8): List of terms, logical AND
*
- * Search through applications for the given search terms. Note that returned
- * strings are only valid until a return to the main loop.
+ * Search through applications for the given search terms.
*
- * Returns: (transfer container) (element-type utf8): List of application identifiers
+ * Returns: (transfer container) (element-type ShellApp): List of applications
*/
GSList *
shell_app_system_initial_search (ShellAppSystem *self,
- gboolean prefs,
GSList *terms)
{
- return shell_app_system_initial_search_internal (self, terms,
- prefs ? self->priv->cached_settings : self->priv->cached_flattened_apps);
+ return search_tree (self, terms, self->priv->entry_to_app);
}
/**
* shell_app_system_subsearch:
* @system: A #ShellAppSystem
- * @prefs: %TRUE if we should search preferences instead of apps
- * @previous_results: (element-type utf8): List of previous results
+ * @previous_results: (element-type ShellApp): List of previous results
* @terms: (element-type utf8): List of terms, logical AND
*
* Search through a previous result set; for more information, see
* the same as passed to shell_app_system_initial_search(). Note that returned
* strings are only valid until a return to the main loop.
*
- * Returns: (transfer container) (element-type utf8): List of application identifiers
+ * Returns: (transfer container) (element-type ShellApp): List of application identifiers
*/
GSList *
shell_app_system_subsearch (ShellAppSystem *system,
- gboolean prefs,
GSList *previous_results,
GSList *terms)
{
GSList *substring_results = NULL;
GSList *normalized_terms = normalize_terms (terms);
- /* Note prefs is deliberately ignored; both apps and prefs are in app_id_to_app,
- * but we have the parameter for consistency and in case in the future
- * they're not in the same data structure.
- */
-
for (iter = previous_results; iter; iter = iter->next)
{
- const char *id = iter->data;
- ShellAppInfo *info;
-
- info = g_hash_table_lookup (system->priv->app_id_to_info, id);
- if (!info)
- continue;
-
- shell_app_system_do_match (system, info, normalized_terms,
- &multiple_prefix_results, &prefix_results,
- &multiple_substring_results, &substring_results);
+ ShellApp *app = iter->data;
+
+ _shell_app_do_match (app, normalized_terms,
+ &multiple_prefix_results, &prefix_results,
+ &multiple_substring_results, &substring_results);
}
g_slist_foreach (normalized_terms, (GFunc)g_free, NULL);
g_slist_free (normalized_terms);
return sort_and_concat_results (system, multiple_prefix_results, prefix_results, multiple_substring_results, substring_results);
}
-const char *
-shell_app_info_get_id (ShellAppInfo *info)
-{
- switch (info->type)
- {
- case SHELL_APP_INFO_TYPE_ENTRY:
- return gmenu_tree_entry_get_desktop_file_id (info->entry);
- case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
- return info->keyfile_path;
- case SHELL_APP_INFO_TYPE_WINDOW:
- return info->window_id;
- }
- g_assert_not_reached ();
- return NULL;
-}
-
-static char *
-shell_app_info_get_prefix (ShellAppInfo *info)
-{
- char *prefix = NULL, *file_prefix = NULL;
- const char *id;
- GFile *file;
- char *name;
- int i = 0;
-
- if (info->type != SHELL_APP_INFO_TYPE_ENTRY)
- return NULL;
-
- id = gmenu_tree_entry_get_desktop_file_id (info->entry);
- file = g_file_new_for_path (gmenu_tree_entry_get_desktop_file_path (info->entry));
- name = g_file_get_basename (file);
-
- if (!name)
- {
- g_object_unref (file);
- return NULL;
- }
- for (i = 0; vendor_prefixes[i]; i++)
- {
- if (g_str_has_prefix (name, vendor_prefixes[i]))
- {
- file_prefix = g_strdup (vendor_prefixes[i]);
- break;
- }
- }
-
- while (strcmp (name, id) != 0)
- {
- char *t;
- char *pname;
- GFile *parent = g_file_get_parent (file);
-
- if (!parent)
- {
- g_warn_if_reached ();
- break;
- }
-
- pname = g_file_get_basename (parent);
- if (!pname)
- {
- g_object_unref (parent);
- break;
- }
- if (!g_strstr_len (id, -1, pname))
- {
- /* handle <LegacyDir prefix="..."> */
- char *t;
- size_t name_len = strlen (name);
- size_t id_len = strlen (id);
- char *t_id = g_strdup (id);
-
- t_id[id_len - name_len] = '\0';
- t = g_strdup(t_id);
- g_free (prefix);
- g_free (t_id);
- g_free (name);
- name = g_strdup (id);
- prefix = t;
-
- g_object_unref (file);
- file = parent;
- g_free (pname);
- g_free (file_prefix);
- file_prefix = NULL;
- break;
- }
-
- t = g_strconcat (pname, "-", name, NULL);
- g_free (name);
- name = t;
-
- t = g_strconcat (pname, "-", prefix, NULL);
- g_free (prefix);
- prefix = t;
-
- g_object_unref (file);
- file = parent;
- g_free (pname);
- }
-
- if (file)
- g_object_unref (file);
-
- if (strcmp (name, id) == 0)
- {
- g_free (name);
- if (file_prefix && !prefix)
- return file_prefix;
- if (file_prefix)
- {
- char *t = g_strconcat (prefix, "-", file_prefix, NULL);
- g_free (prefix);
- g_free (file_prefix);
- prefix = t;
- }
- return prefix;
- }
-
- g_free (name);
- g_free (prefix);
- g_free (file_prefix);
- g_return_val_if_reached (NULL);
-}
-
-#define DESKTOP_ENTRY_GROUP "Desktop Entry"
-
-char *
-shell_app_info_get_name (ShellAppInfo *info)
-{
- switch (info->type)
- {
- case SHELL_APP_INFO_TYPE_ENTRY:
- {
- const char *name = g_app_info_get_name (G_APP_INFO (gmenu_tree_entry_get_app_info (info->entry)));
- return g_strdup (name);
- }
- case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
- return g_key_file_get_locale_string (info->keyfile, DESKTOP_ENTRY_GROUP, "Name", NULL, NULL);
- case SHELL_APP_INFO_TYPE_WINDOW:
- {
- const char *name;
-
- name = meta_window_get_wm_class (info->window);
- if (!name)
- name = _("Unknown");
- return g_strdup (name);
- }
- }
- g_assert_not_reached ();
- return NULL;
-}
-
-char *
-shell_app_info_get_description (ShellAppInfo *info)
-{
- switch (info->type)
- {
- case SHELL_APP_INFO_TYPE_ENTRY:
- {
- const char *description = g_app_info_get_description (G_APP_INFO (gmenu_tree_entry_get_app_info (info->entry)));
- return g_strdup (description);
- }
- case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
- return g_key_file_get_locale_string (info->keyfile, DESKTOP_ENTRY_GROUP, "Comment", NULL, NULL);
- case SHELL_APP_INFO_TYPE_WINDOW:
- return NULL;
- }
- g_assert_not_reached ();
- return NULL;
-}
-
-char *
-shell_app_info_get_executable (ShellAppInfo *info)
-{
- switch (info->type)
- {
- case SHELL_APP_INFO_TYPE_ENTRY:
- {
- const char *exec = g_app_info_get_executable (G_APP_INFO (gmenu_tree_entry_get_app_info (info->entry)));
- return g_strdup (exec);
- }
- case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
- return g_key_file_get_string (info->keyfile, DESKTOP_ENTRY_GROUP, "Exec", NULL);
- case SHELL_APP_INFO_TYPE_WINDOW:
- return NULL;
- }
- g_assert_not_reached ();
- return NULL;
-}
-
-char *
-shell_app_info_get_desktop_file_path (ShellAppInfo *info)
-{
- switch (info->type)
- {
- case SHELL_APP_INFO_TYPE_ENTRY:
- return g_strdup (gmenu_tree_entry_get_desktop_file_path (info->entry));
- case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
- return g_strdup (info->keyfile_path);
- case SHELL_APP_INFO_TYPE_WINDOW:
- return NULL;
- }
- g_assert_not_reached ();
- return NULL;
-}
-
-static GIcon *
-themed_icon_from_name (const char *iconname)
-{
- GIcon *icon;
-
- if (!iconname)
- return NULL;
-
- if (g_path_is_absolute (iconname))
- {
- GFile *file;
- file = g_file_new_for_path (iconname);
- icon = G_ICON (g_file_icon_new (file));
- g_object_unref (file);
- }
- else
- {
- char *tmp_name, *p;
- tmp_name = strdup (iconname);
- /* Work around a common mistake in desktop files */
- if ((p = strrchr (tmp_name, '.')) != NULL &&
- (strcmp (p, ".png") == 0 ||
- strcmp (p, ".xpm") == 0 ||
- strcmp (p, ".svg") == 0))
- {
- *p = 0;
- }
- icon = g_themed_icon_new (tmp_name);
- g_free (tmp_name);
- }
-
- return icon;
-}
-
-/**
- * shell_app_info_get_icon:
- * @info: A #ShellAppInfo
- *
- * Get the #GIcon associated with this app; for apps "faked" from a #MetaWindow,
- * return %NULL.
- *
- * Returns: (transfer full): The icon for @info, or %NULL
- */
-GIcon *
-shell_app_info_get_icon (ShellAppInfo *info)
-{
- char *iconname = NULL;
- GIcon *icon;
-
- /* This code adapted from gdesktopappinfo.c
- * Copyright (C) 2006-2007 Red Hat, Inc.
- * Copyright © 2007 Ryan Lortie
- * LGPL
- */
-
- switch (info->type)
- {
- case SHELL_APP_INFO_TYPE_ENTRY:
- return g_object_ref (g_app_info_get_icon (G_APP_INFO (gmenu_tree_entry_get_app_info (info->entry))));
- case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
- iconname = g_key_file_get_locale_string (info->keyfile, DESKTOP_ENTRY_GROUP, "Icon", NULL, NULL);
- icon = themed_icon_from_name (iconname);
- g_free (iconname);
- return icon;
- break;
- case SHELL_APP_INFO_TYPE_WINDOW:
- return NULL;
- }
- g_assert_not_reached ();
- return NULL;
-}
-
-/**
- * shell_app_system_get_sections:
- *
- * return names of sections in applications menu.
- *
- * Returns: (element-type utf8) (transfer full): List of Names
- */
-GList *
-shell_app_system_get_sections (ShellAppSystem *system)
-{
- GList *res = NULL;
- GMenuTreeDirectory *root;
- GMenuTreeIter *iter;
- GMenuTreeItemType next_type;
-
- root = gmenu_tree_get_root_directory (system->priv->apps_tree);
-
- if (G_UNLIKELY (!root))
- g_error ("applications.menu not found.");
-
- iter = gmenu_tree_directory_iter (root);
-
- while ((next_type = gmenu_tree_iter_next (iter)) != GMENU_TREE_ITEM_INVALID)
- {
- if (next_type == GMENU_TREE_ITEM_DIRECTORY)
- {
- GMenuTreeDirectory *dir = gmenu_tree_iter_get_directory (iter);
- char *name = g_strdup (gmenu_tree_directory_get_name (dir));
-
- g_assert (name);
-
- res = g_list_append (res, name);
- gmenu_tree_item_unref (dir);
- }
- }
-
- gmenu_tree_iter_unref (iter);
-
- return res;
-}
-
/**
- * shell_app_info_get_section:
- *
- * return name of section, that contain this application.
- * Returns: (transfer full): section name
- */
-char *
-shell_app_info_get_section (ShellAppInfo *info)
-{
- char *name;
- GMenuTreeDirectory *dir, *parent;
-
- if (info->type != SHELL_APP_INFO_TYPE_ENTRY)
- return NULL;
-
- dir = gmenu_tree_entry_get_parent (info->entry);
- if (!dir)
- return NULL;
-
- parent = gmenu_tree_directory_get_parent (dir);
- if (!parent)
- return NULL;
-
- while (TRUE)
- {
- GMenuTreeDirectory *pparent = gmenu_tree_directory_get_parent (parent);
- if (!pparent)
- break;
- gmenu_tree_item_unref (dir);
- dir = parent;
- parent = pparent;
- }
-
- name = g_strdup (gmenu_tree_directory_get_name (dir));
-
- gmenu_tree_item_unref (dir);
- gmenu_tree_item_unref (parent);
- return name;
-}
-
-gboolean
-shell_app_info_get_is_nodisplay (ShellAppInfo *info)
-{
- switch (info->type)
- {
- case SHELL_APP_INFO_TYPE_ENTRY:
- return g_desktop_app_info_get_nodisplay (gmenu_tree_entry_get_app_info (info->entry));
- case SHELL_APP_INFO_TYPE_DESKTOP_FILE:
- case SHELL_APP_INFO_TYPE_WINDOW:
- return FALSE;
- }
- g_assert_not_reached ();
- return TRUE;
-}
-
-/**
- * shell_app_info_is_transient:
- *
- * A "transient" application is one which represents
- * just an open window, i.e. we don't know how to launch it
- * again.
- */
-gboolean
-shell_app_info_is_transient (ShellAppInfo *info)
-{
- return info->type == SHELL_APP_INFO_TYPE_WINDOW;
-}
-
-/**
- * shell_app_info_create_icon_texture:
- *
- * Look up the icon for this application, and create a #ClutterTexture
- * for it at the given size.
+ * shell_app_system_search_settings:
+ * @system: A #ShellAppSystem
+ * @terms: (element-type utf8): List of terms, logical AND
*
- * Return value: (transfer none): A floating #ClutterActor
- */
-ClutterActor *
-shell_app_info_create_icon_texture (ShellAppInfo *info, float size)
-{
- GIcon *icon;
- ClutterActor *ret;
-
- ret = NULL;
-
- if (info->type == SHELL_APP_INFO_TYPE_WINDOW)
- {
- ret = st_texture_cache_bind_pixbuf_property (st_texture_cache_get_default (),
- G_OBJECT (info->window),
- "icon");
- }
- else
- {
- icon = shell_app_info_get_icon (info);
- if (icon != NULL)
- {
- ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), NULL, icon, (int)size);
- g_object_unref (icon);
- }
- }
-
- if (ret == NULL)
- {
- icon = g_themed_icon_new ("application-x-executable");
- ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), NULL, icon, (int)size);
- g_object_unref (icon);
- }
-
- return ret;
-}
-
-/**
- * shell_app_info_get_source_window:
- * @info: A #ShellAppInfo
+ * Search through settings for the given search terms.
*
- * Returns: (transfer none): If @info is tracking a #MetaWindow,
- * return that window. Otherwise, return %NULL.
- */
-MetaWindow *
-shell_app_info_get_source_window (ShellAppInfo *info)
-{
- if (info->type == SHELL_APP_INFO_TYPE_WINDOW)
- return info->window;
- return NULL;
-}
-
-static void
-_gather_pid_callback (GDesktopAppInfo *gapp,
- GPid pid,
- gpointer data)
-{
- ShellApp *app;
- ShellWindowTracker *tracker;
-
- g_return_if_fail (data != NULL);
-
- app = SHELL_APP (data);
- tracker = shell_window_tracker_get_default ();
-
- _shell_window_tracker_add_child_process_app (tracker,
- pid,
- app);
-}
-
-/**
- * shell_app_info_launch_full:
- * @timestamp: Event timestamp, or 0 for current event timestamp
- * @uris: List of uris to pass to application
- * @workspace: Start on this workspace, or -1 for default
- * @startup_id: (out): Returned startup notification ID, or %NULL if none
- * @error: A #GError
+ * Returns: (transfer container) (element-type ShellApp): List of setting applications
*/
-gboolean
-shell_app_info_launch_full (ShellAppInfo *info,
- guint timestamp,
- GList *uris,
- int workspace,
- char **startup_id,
- GError **error)
-{
- ShellApp *shell_app;
- GDesktopAppInfo *gapp;
- GdkAppLaunchContext *context;
- gboolean ret;
- ShellGlobal *global;
- MetaScreen *screen;
-
- if (startup_id)
- *startup_id = NULL;
-
- if (info->type == SHELL_APP_INFO_TYPE_WINDOW)
- {
- /* We can't pass URIs into a window; shouldn't hit this
- * code path. If we do, fix the caller to disallow it.
- */
- g_return_val_if_fail (uris == NULL, TRUE);
-
- meta_window_activate (info->window, timestamp);
- return TRUE;
- }
- else if (info->type == SHELL_APP_INFO_TYPE_ENTRY)
- {
- /* Can't use g_desktop_app_info_new, see bug 614879 */
- const char *filename = gmenu_tree_entry_get_desktop_file_path (info->entry);
- gapp = g_desktop_app_info_new_from_filename (filename);
- }
- else
- {
- char *filename = shell_app_info_get_desktop_file_path (info);
- gapp = g_desktop_app_info_new_from_filename (filename);
- g_free (filename);
- }
-
- if (!gapp)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Not found");
- return FALSE;
- }
-
- global = shell_global_get ();
- screen = shell_global_get_screen (global);
-
- if (timestamp == 0)
- timestamp = clutter_get_current_event_time ();
-
- if (workspace < 0)
- workspace = meta_screen_get_active_workspace_index (screen);
-
- context = gdk_app_launch_context_new ();
- gdk_app_launch_context_set_timestamp (context, timestamp);
- gdk_app_launch_context_set_desktop (context, workspace);
-
- shell_app = shell_app_system_get_app (shell_app_system_get_default (),
- shell_app_info_get_id (info));
-
- /* In the case where we know an app, we handle reaping the child internally,
- * in the window tracker.
- */
- if (shell_app != NULL)
- ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris,
- G_APP_LAUNCH_CONTEXT (context),
- G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
- NULL, NULL,
- _gather_pid_callback, shell_app,
- error);
- else
- ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris,
- G_APP_LAUNCH_CONTEXT (context),
- G_SPAWN_SEARCH_PATH,
- NULL, NULL,
- NULL, NULL,
- error);
-
- g_object_unref (G_OBJECT (gapp));
-
- return ret;
-}
-
-gboolean
-shell_app_info_launch (ShellAppInfo *info,
- GError **error)
+GSList *
+shell_app_system_search_settings (ShellAppSystem *self,
+ GSList *terms)
{
- return shell_app_info_launch_full (info, 0, NULL, -1, NULL, error);
+ return search_tree (self, terms, self->priv->setting_entry_to_app);
}
#include "shell-window-tracker-private.h"
#include "st.h"
+typedef enum {
+ MATCH_NONE,
+ MATCH_SUBSTRING, /* Not prefix, substring */
+ MATCH_MULTIPLE_SUBSTRING, /* Matches multiple criteria with substrings */
+ MATCH_PREFIX, /* Strict prefix */
+ MATCH_MULTIPLE_PREFIX, /* Matches multiple criteria, at least one prefix */
+} ShellAppSearchMatch;
+
/* This is mainly a memory usage optimization - the user is going to
* be running far fewer of the applications at one time than they have
* installed. But it also just helps keep the code more logically
* SECTION:shell-app
* @short_description: Object representing an application
*
- * This object wraps a #ShellAppInfo, providing methods and signals
+ * This object wraps a #GMenuTreeEntry, providing methods and signals
* primarily useful for running applications.
*/
struct _ShellApp
ShellAppState state;
- ShellAppInfo *info;
+ GMenuTreeEntry *entry; /* If NULL, this app is backend by exactly one MetaWindow */
ShellAppRunningState *running_state;
+
+ char *window_id_string;
+
+ char *casefolded_name;
+ char *name_collation_key;
+ char *casefolded_description;
+ char *casefolded_exec;
};
G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT);
const char *
shell_app_get_id (ShellApp *app)
{
- return shell_app_info_get_id (app->info);
+ if (app->entry)
+ return gmenu_tree_entry_get_desktop_file_id (app->entry);
+ return app->window_id_string;
+}
+
+static MetaWindow *
+window_backed_app_get_window (ShellApp *app)
+{
+ g_assert (app->entry == NULL);
+ g_assert (app->running_state);
+ g_assert (app->running_state->windows);
+ return app->running_state->windows->data;
}
/**
shell_app_create_icon_texture (ShellApp *app,
float size)
{
- return shell_app_info_create_icon_texture (app->info, size);
+ GIcon *icon;
+ ClutterActor *ret;
+
+ ret = NULL;
+
+ if (app->entry == NULL)
+ {
+ MetaWindow *window = window_backed_app_get_window (app);
+ return st_texture_cache_bind_pixbuf_property (st_texture_cache_get_default (),
+ G_OBJECT (window),
+ "icon");
+ }
+
+ icon = g_app_info_get_icon (G_APP_INFO (gmenu_tree_entry_get_app_info (app->entry)));
+ if (icon != NULL)
+ ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), NULL, icon, (int)size);
+
+ if (ret == NULL)
+ {
+ icon = g_themed_icon_new ("application-x-executable");
+ ret = st_texture_cache_load_gicon (st_texture_cache_get_default (), NULL, icon, (int)size);
+ g_object_unref (icon);
+ }
+
+ return ret;
}
+
typedef struct {
ShellApp *app;
int size;
info = NULL;
- icon = shell_app_info_get_icon (app->info);
+ icon = g_app_info_get_icon (G_APP_INFO (gmenu_tree_entry_get_app_info (app->entry)));
if (icon != NULL)
{
info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (),
icon, (int) (size + 0.5),
GTK_ICON_LOOKUP_FORCE_SIZE);
- g_object_unref (icon);
}
if (info == NULL)
ClutterActor *
shell_app_get_faded_icon (ShellApp *app, float size)
{
- MetaWindow *window;
CoglHandle texture;
ClutterActor *result;
char *cache_key;
CreateFadedIconData data;
- /* Punt for WINDOW types for now...easier to reuse the property tracking bits,
- * and this helps us visually distinguish app-tracked from not.
+ /* Don't fade for window backed apps for now...easier to reuse the
+ * property tracking bits, and this helps us visually distinguish
+ * app-tracked from not.
*/
- window = shell_app_info_get_source_window (app->info);
- if (window)
+ if (!app->entry)
{
+ MetaWindow *window = window_backed_app_get_window (app);
return st_texture_cache_bind_pixbuf_property (st_texture_cache_get_default (),
G_OBJECT (window),
"icon");
}
+
cache_key = g_strdup_printf ("faded-icon:%s,size=%f", shell_app_get_id (app), size);
data.app = app;
return result;
}
-char *
+const char *
shell_app_get_name (ShellApp *app)
{
- return shell_app_info_get_name (app->info);
+ if (app->entry)
+ return g_app_info_get_name (G_APP_INFO (gmenu_tree_entry_get_app_info (app->entry)));
+ else
+ {
+ MetaWindow *window = window_backed_app_get_window (app);
+ const char *name;
+
+ name = meta_window_get_wm_class (window);
+ if (!name)
+ name = _("Unknown");
+ return name;
+ }
}
-char *
+const char *
shell_app_get_description (ShellApp *app)
{
- return shell_app_info_get_description (app->info);
+ if (app->entry)
+ return g_app_info_get_description (G_APP_INFO (gmenu_tree_entry_get_app_info (app->entry)));
+ else
+ return NULL;
}
+/**
+ * shell_app_is_window_backed:
+ *
+ * A window backed application is one which represents just an open
+ * window, i.e. there's no .desktop file assocation, so we don't know
+ * how to launch it again.
+ */
gboolean
-shell_app_is_transient (ShellApp *app)
+shell_app_is_window_backed (ShellApp *app)
{
- return shell_app_info_is_transient (app->info);
+ return app->entry == NULL;
}
typedef struct {
case SHELL_APP_STATE_STOPPED:
{
GError *error = NULL;
- if (!shell_app_info_launch_full (app->info,
- 0,
- NULL,
- workspace,
- NULL,
- &error))
+ if (!shell_app_launch (app,
+ 0,
+ NULL,
+ workspace,
+ NULL,
+ &error))
{
char *msg;
msg = g_strdup_printf (_("Failed to launch '%s'"), shell_app_get_name (app));
shell_app_open_new_window (ShellApp *app,
int workspace)
{
+ g_return_if_fail (app->entry != NULL);
+
/* Here we just always launch the application again, even if we know
* it was already running. For most applications this
* should have the effect of creating a new window, whether that's
* as say Pidgin. Ideally, we have the application express to us
* that it supports an explicit new-window action.
*/
- shell_app_info_launch_full (app->info,
- 0,
- NULL,
- workspace,
- NULL,
- NULL);
+ shell_app_launch (app,
+ 0,
+ NULL,
+ workspace,
+ NULL,
+ NULL);
}
/**
return app->state;
}
-/**
- * _shell_app_get_info:
- *
- * Returns: (transfer none): Associated app info
- */
-ShellAppInfo *
-_shell_app_get_info (ShellApp *app)
-{
- return app->info;
-}
-
typedef struct {
ShellApp *app;
MetaWorkspace *active_workspace;
ShellApp *app;
app = g_object_new (SHELL_TYPE_APP, NULL);
- app->info = shell_app_system_create_from_window (shell_app_system_get_default (), window);
- _shell_app_system_register_app (shell_app_system_get_default (), app);
+
+ /* For windows, its id is simply its pointer address as a string.
+ * There are various other alternatives, but the address is unique
+ * and unchanging, which is pretty much the best we can do.
+ */
+ app->window_id_string = g_strdup_printf ("window:%p", window);
+
_shell_app_add_window (app, window);
return app;
}
ShellApp *
-_shell_app_new (ShellAppInfo *info)
+_shell_app_new (GMenuTreeEntry *info)
{
ShellApp *app;
app = g_object_new (SHELL_TYPE_APP, NULL);
- app->info = shell_app_info_ref (info);
- _shell_app_system_register_app (shell_app_system_get_default (), app);
+ app->entry = gmenu_tree_item_ref (info);
+ app->name_collation_key = g_utf8_collate_key (shell_app_get_name (app), -1);
return app;
}
return TRUE;
}
+static void
+_gather_pid_callback (GDesktopAppInfo *gapp,
+ GPid pid,
+ gpointer data)
+{
+ ShellApp *app;
+ ShellWindowTracker *tracker;
+
+ g_return_if_fail (data != NULL);
+
+ app = SHELL_APP (data);
+ tracker = shell_window_tracker_get_default ();
+
+ _shell_window_tracker_add_child_process_app (tracker,
+ pid,
+ app);
+}
+
+/**
+ * shell_app_launch:
+ * @timestamp: Event timestamp, or 0 for current event timestamp
+ * @uris: List of uris to pass to application
+ * @workspace: Start on this workspace, or -1 for default
+ * @startup_id: (out): Returned startup notification ID, or %NULL if none
+ * @error: A #GError
+ */
+gboolean
+shell_app_launch (ShellApp *app,
+ guint timestamp,
+ GList *uris,
+ int workspace,
+ char **startup_id,
+ GError **error)
+{
+ GDesktopAppInfo *gapp;
+ GdkAppLaunchContext *context;
+ gboolean ret;
+ ShellGlobal *global;
+ MetaScreen *screen;
+
+ if (startup_id)
+ *startup_id = NULL;
+
+ if (app->entry == NULL)
+ {
+ MetaWindow *window = window_backed_app_get_window (app);
+ /* We can't pass URIs into a window; shouldn't hit this
+ * code path. If we do, fix the caller to disallow it.
+ */
+ g_return_val_if_fail (uris == NULL, TRUE);
+
+ meta_window_activate (window, timestamp);
+ return TRUE;
+ }
+
+ global = shell_global_get ();
+ screen = shell_global_get_screen (global);
+
+ if (timestamp == 0)
+ timestamp = clutter_get_current_event_time ();
+
+ if (workspace < 0)
+ workspace = meta_screen_get_active_workspace_index (screen);
+
+ context = gdk_app_launch_context_new ();
+ gdk_app_launch_context_set_timestamp (context, timestamp);
+ gdk_app_launch_context_set_desktop (context, workspace);
+
+ gapp = gmenu_tree_entry_get_app_info (app->entry);
+ ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris,
+ G_APP_LAUNCH_CONTEXT (context),
+ G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL,
+ _gather_pid_callback, app,
+ error);
+ g_object_unref (context);
+
+ return ret;
+}
+
+/**
+ * shell_app_get_app_info:
+ * @app: a #ShellApp
+ *
+ * Returns: (transfer none): The #GDesktopAppInfo for this app, or %NULL if backed by a window
+ */
+GDesktopAppInfo *
+shell_app_get_app_info (ShellApp *app)
+{
+ if (app->entry)
+ return gmenu_tree_entry_get_app_info (app->entry);
+ return NULL;
+}
+
+/**
+ * shell_app_get_tree_entry:
+ * @app: a #ShellApp
+ *
+ * Returns: (transfer none): The #GMenuTreeEntry for this app, or %NULL if backed by a window
+ */
+GMenuTreeEntry *
+shell_app_get_tree_entry (ShellApp *app)
+{
+ return app->entry;
+}
+
static void
create_running_state (ShellApp *app)
{
g_slice_free (ShellAppRunningState, state);
}
+static char *
+normalize_and_casefold (const char *str)
+{
+ char *normalized, *result;
+
+ if (str == NULL)
+ return NULL;
+
+ normalized = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
+ result = g_utf8_casefold (normalized, -1);
+ g_free (normalized);
+ return result;
+}
+
+static char *
+trim_exec_line (const char *str)
+{
+ const char *start, *end, *pos;
+
+ end = strchr (str, ' ');
+ if (end == NULL)
+ end = str + strlen (str);
+
+ start = str;
+ while ((pos = strchr (start, '/')) && pos < end)
+ start = ++pos;
+
+ return g_strndup (start, end - start);
+}
+
+static void
+shell_app_init_search_data (ShellApp *app)
+{
+ const char *name;
+ const char *exec;
+ const char *comment;
+ char *normalized_exec;
+ GDesktopAppInfo *appinfo;
+
+ appinfo = gmenu_tree_entry_get_app_info (app->entry);
+ name = g_app_info_get_name (G_APP_INFO (appinfo));
+ app->casefolded_name = normalize_and_casefold (name);
+
+ comment = g_app_info_get_description (G_APP_INFO (appinfo));
+ app->casefolded_description = normalize_and_casefold (comment);
+
+ exec = g_app_info_get_executable (G_APP_INFO (appinfo));
+ normalized_exec = normalize_and_casefold (exec);
+ app->casefolded_exec = trim_exec_line (normalized_exec);
+ g_free (normalized_exec);
+}
+
+/**
+ * shell_app_compare_by_name:
+ * @app:
+ * @other:
+ *
+ * Order two applications by name.
+ *
+ * Returns: -1, 0, or 1; suitable for use as a comparison function for e.g. g_slist_sort()
+ */
+int
+shell_app_compare_by_name (ShellApp *app, ShellApp *other)
+{
+ return strcmp (app->name_collation_key, other->name_collation_key);
+}
+
+static ShellAppSearchMatch
+_shell_app_match_search_terms (ShellApp *app,
+ GSList *terms)
+{
+ GSList *iter;
+ ShellAppSearchMatch match;
+
+ if (G_UNLIKELY (!app->casefolded_name))
+ shell_app_init_search_data (app);
+
+ match = MATCH_NONE;
+ for (iter = terms; iter; iter = iter->next)
+ {
+ ShellAppSearchMatch current_match;
+ const char *term = iter->data;
+ const char *p;
+
+ current_match = MATCH_NONE;
+
+ p = strstr (app->casefolded_name, term);
+ if (p == app->casefolded_name)
+ current_match = MATCH_PREFIX;
+ else if (p != NULL)
+ current_match = MATCH_SUBSTRING;
+
+ p = strstr (app->casefolded_exec, term);
+ if (p != NULL)
+ {
+ if (p == app->casefolded_exec)
+ current_match = (current_match == MATCH_NONE) ? MATCH_PREFIX
+ : MATCH_MULTIPLE_PREFIX;
+ else if (current_match < MATCH_PREFIX)
+ current_match = (current_match == MATCH_NONE) ? MATCH_SUBSTRING
+ : MATCH_MULTIPLE_SUBSTRING;
+ }
+
+ if (app->casefolded_description && current_match < MATCH_PREFIX)
+ {
+ /* Only do substring matches, as prefix matches are not meaningful
+ * enough for descriptions
+ */
+ p = strstr (app->casefolded_description, term);
+ if (p != NULL)
+ current_match = (current_match == MATCH_NONE) ? MATCH_SUBSTRING
+ : MATCH_MULTIPLE_SUBSTRING;
+ }
+
+ if (current_match == MATCH_NONE)
+ return current_match;
+
+ if (current_match > match)
+ match = current_match;
+ }
+ return match;
+}
+
+void
+_shell_app_do_match (ShellApp *app,
+ GSList *terms,
+ GSList **multiple_prefix_results,
+ GSList **prefix_results,
+ GSList **multiple_substring_results,
+ GSList **substring_results)
+{
+ ShellAppSearchMatch match;
+ GAppInfo *appinfo;
+
+ g_assert (app != NULL);
+
+ /* Skip window-backed apps */
+ appinfo = (GAppInfo*)shell_app_get_app_info (app);
+ if (appinfo == NULL)
+ return;
+ /* Skip not-visible apps */
+ if (!g_app_info_should_show (appinfo))
+ return;
+
+ match = _shell_app_match_search_terms (app, terms);
+ switch (match)
+ {
+ case MATCH_NONE:
+ break;
+ case MATCH_MULTIPLE_PREFIX:
+ *multiple_prefix_results = g_slist_prepend (*multiple_prefix_results, app);
+ break;
+ case MATCH_PREFIX:
+ *prefix_results = g_slist_prepend (*prefix_results, app);
+ break;
+ case MATCH_MULTIPLE_SUBSTRING:
+ *multiple_substring_results = g_slist_prepend (*multiple_substring_results, app);
+ break;
+ case MATCH_SUBSTRING:
+ *substring_results = g_slist_prepend (*substring_results, app);
+ break;
+ }
+}
+
+
static void
shell_app_init (ShellApp *self)
{
{
ShellApp *app = SHELL_APP (object);
- if (app->info)
+ if (app->entry)
{
- shell_app_info_unref (app->info);
- app->info = NULL;
+ gmenu_tree_item_unref (app->entry);
+ app->entry = NULL;
}
if (app->running_state)
G_OBJECT_CLASS(shell_app_parent_class)->dispose (object);
}
+static void
+shell_app_finalize (GObject *object)
+{
+ ShellApp *app = SHELL_APP (object);
+
+ g_free (app->window_id_string);
+
+ g_free (app->casefolded_name);
+ g_free (app->name_collation_key);
+ g_free (app->casefolded_description);
+
+ G_OBJECT_CLASS(shell_app_parent_class)->finalize (object);
+}
+
static void
shell_app_class_init(ShellAppClass *klass)
{
gobject_class->get_property = shell_app_get_property;
gobject_class->dispose = shell_app_dispose;
+ gobject_class->finalize = shell_app_finalize;
shell_app_signals[WINDOWS_CHANGED] = g_signal_new ("windows-changed",
SHELL_TYPE_APP,